// echo server->client->server
// 客户端给服务器发消息，服务器接收到消息之后，内部走一圈，再返回给客户端
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include "LockGuard.hpp"
#include "Log.hpp"
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <strings.h>
#include <netinet/in.h>
#include "InetAddr.hpp"
#include <functional>
using namespace std;

enum
{
    SOCKET_ERROR = 1, // 套接字错误
    BIND_ERROR = 2,   // 绑定失败
    USAGE_ERROR = 3,  // 格式错误
};
const static int defaultfd = -1;
// 使用仿函数
using func_t = function<string(const string &, bool &)>;
// using func_t(函数对象、函数指针) =function<返回值(参数类型)>;

class UdpServer
{
public:
    // 由外部传入端口号
    UdpServer(uint16_t port /*, const string &ip*/, func_t func)
        : _sockfd(defaultfd), _port(port), /*_ip(ip),*/ _isrunning(false), _func(func)
    {
    }
    ~UdpServer()
    {
    }
    void InitServer()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0) // 创建失败检测
        {
            LOG(FATAL, "创建socket套接字失败,错误信息:%s,错误码:%d\n", strerror(errno), errno);
            exit(SOCKET_ERROR);
        }
        // 创建套接字成功
        LOG(Info, "创建socket套接字成功,套接字:%d\n", _sockfd);

        // 填充sockaddr_in结构
        struct sockaddr_in local;
        // 清理local开辟的栈空间
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;    // 地址类型
        local.sin_port = htons(_port); // 端口号
        // port端口号要经过网络传输传给对面主机，先将端口号传输到网络，在传输到对方主机，
        // 端口号是主机序列，要将主机序列转为网络序列
        // 1、将字符串风格的点分十进制ip地址转化为4字节ip地址
        // 此时即便ip地址是4字节，但还是主机序列，要将主机序列转化为网络序列
        // 2、将4字节ip地址转化为网络序列
        // inet_addr接口将16进制ip地址转化为4字节，再将4字节转化为网络序列
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // ip地址
        local.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY这个宏就是0，在服务器端，ip地址设置为0即可

        // 创建ip地址 "xx.xx.xx.xx"->点分十进制ip地址,网络使用的是4字节风格的ip地址
        // 需要将点分十进制的ip地址转换为四字节的ip地址

        // 绑定套接字文件描述符与网络信息
        int n = bind(_sockfd, (const struct sockaddr *)&local, sizeof(local));
        if (n < 0) // 检测绑定失败
        {
            LOG(FATAL, "绑定失败，错误信息:%s,错误码:%d\n", strerror(errno), errno);
            exit(BIND_ERROR);
        }
        // 绑定成功
        LOG(Info, "绑定成功，套接字文件描述符:%d\n", _sockfd);
    }
    void Start()
    {
        // 服务是一直在运行，知道管理者不想运行了，服务器都是死循环
        _isrunning = true;
        // UDP协议是面向数据报的
        while (true)
        {
            // 让服务器收数据
            // 定义接收数据的缓冲区
            char request[1024];
            // 定义接收网络信息的缓冲区
            struct sockaddr_in pree;
            socklen_t len = sizeof(pree); // 必须初始化为sizeof(pree)
            // 读取数据
            ssize_t n = recvfrom(_sockfd, request, sizeof(request) - 1, 0, (struct sockaddr *)&pree, &len);
            if (n > 0) // 获取信息成功
            {
                request[n] = 0;
                InetAddr addr(pree);
                LOG(DEBUG, "收到的信息为:%s,客户端ip地址:%s,客户端端口号:%d\n", request, addr.Ip().c_str(), addr.Port());
                // 将发送过来的数据翻译-将请求回调出去，在外部进行处理
                bool ok;
                string response = _func(request, ok);
                // (void)ok;
                // ok为false,表示不可信，ok为true，表示可信
                //  将服务器收到的数据，发送给客户端
                sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&pree, len);
            }
        }
        _isrunning = false;
    }

private:
    int _sockfd;    // 套接字文件描述符
    uint16_t _port; // 服务器所用的端口号
    // string _ip;      // ip地址-----ip地址设置为0即可
    bool _isrunning; // 服务是否在运行
    func_t _func;    // 给服务器设定回调方法，用来让上层进行注册业务的处理方法
};